/*
 * Copyright (C) 2004 Derek James and Philip Tucker This file is part of ANJI
 * (Another NEAT Java Implementation). ANJI is free software; you can
 * redistribute it and/or modify it under the terms of the GNU General Public
 * License as published by the Free Software Foundation; either version 2 of the
 * License, or (at your option) any later version. This program is distributed
 * in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even
 * the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
 * See the GNU General Public License for more details. You should have received
 * a copy of the GNU General Public License along with this program; if not,
 * write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330,
 * Boston, MA 02111-1307 USA created by Derek James
 */
package com.anji.tournament;

import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Random;
import java.util.Set;
import java.util.Map.Entry;

import org.apache.log4j.Logger;

import com.anji.util.Configurable;
import com.anji.util.Properties;
import com.anji.util.Randomizer;

/**
 * Each contestant plays 1 game against each opponent.
 * @author Philip Tucker
 */
public class DirectTournament extends SimpleTournament implements Configurable {

  private Logger logger = Logger.getLogger(DirectTournament.class);

  private static final String OPPONENTS_KEY = "direct.opponents";

  private Map<PlayerResults, Integer> opponentGameCount =
      new HashMap<PlayerResults, Integer>();

  private PlayerPair currentPlayerPair;

  private Iterator playerIter;

  private Iterator opponentIter;

  private Random rand;

  /**
   * @param aGame
   * @param aWeights
   * @param aRand used to shuffle order of opponents
   */
  public DirectTournament(Game aGame, ScoringWeights aWeights, Random aRand) {
    super(aGame, aWeights);
    rand = aRand;
  }

  /**
   * default ctor; should be followed by <code>init()</code>
   */
  public DirectTournament() {
    // no-op
  }

  /**
   * @see com.anji.util.Configurable#init(com.anji.util.Properties)
   */
  public void init(Properties props) throws Exception {
    super.init(props);
    Randomizer randomizer =
        (Randomizer) props.singletonObjectProperty(Randomizer.class);
    rand = randomizer.getRand();
    Iterator it = props.newObjectListProperty(OPPONENTS_KEY).iterator();
    while (it.hasNext()) {
      addOpponent((Player) it.next());
    }
  }

  /**
   * Package visibility for test.
   * @param opponent
   */
  public void addOpponent(Player opponent) {
    opponentGameCount.put(new PlayerResults(opponent, getScoringWeights()), 0);
  }

  /**
   * @see com.anji.tournament.SimpleTournament#hasNextPlayerPair()
   */
  protected boolean hasNextPlayerPair() {
    return (currentPlayerPair != null)
        && (playerIter.hasNext() || opponentIter.hasNext());
  }

  /**
   * @see com.anji.tournament.SimpleTournament#nextPlayerPair()
   */
  protected PlayerPair nextPlayerPair() {
    if (currentPlayerPair != null) {

      // get next subject if we've hit last opponent
      if (!opponentIter.hasNext()) {
        if (playerIter.hasNext()) {
          currentPlayerPair.contestant = (PlayerResults) playerIter.next();
          currentPlayerPair.opponent = null;
          resetOpponentsIterator();
        } else {
          currentPlayerPair = null;
        }
      }

      if (currentPlayerPair != null) {
        // get next opponent for current subject
        currentPlayerPair.opponent = (PlayerResults) opponentIter.next();
        int count = opponentGameCount.get(currentPlayerPair.opponent);
        opponentGameCount.put(currentPlayerPair.opponent, count + 1);
      }
    }

    return currentPlayerPair;
  }

  private void resetOpponents() {
    Set<Entry<PlayerResults, Integer>> entries = opponentGameCount.entrySet();
    for (Map.Entry<PlayerResults, Integer> entry : entries) {
      entry.setValue(0);
      entry.getKey().getStats().set(0, 0, 0, 0);
    }
    resetOpponentsIterator();
  }

  private void resetOpponentsIterator() {
    // shuffle so that we don't play in the same order every time
    List opponents = new ArrayList(opponentGameCount.keySet());
    Collections.shuffle(opponents, rand);
    opponentIter = opponents.iterator();
  }

  /**
   * @see com.anji.tournament.SimpleTournament#startTournament()
   */
  protected void startTournament() {
    playerIter = getResults().iterator();
    resetOpponents();
    currentPlayerPair = new PlayerPair();

    if (playerIter.hasNext()) {
      currentPlayerPair.contestant = (PlayerResults) playerIter.next();
    }
  }

  /**
   * @see com.anji.tournament.SimpleTournament#getMaxScore()
   */
  public double getMaxScore() {
    return opponentGameCount.size()
        * getGame().getMaxScore(getScoringWeights());
  }

  /**
   * @return <code>List</code> contains opponent <code>PlayerResults</code>
   * objects
   */
  public Set getOpponentsAndResults() {
    return opponentGameCount.keySet();
  }

  /**
   * 
   * @see com.anji.tournament.Tournament#getMinScore()
   */
  public double getMinScore() {
    return opponentGameCount.size()
        * getGame().getMinScore(getScoringWeights());
  }

  /**
   * @see com.anji.tournament.SimpleTournament#endTournament()
   */
  protected void endTournament() {
    int idx = 0;
    Set<Entry<PlayerResults, Integer>> entries = opponentGameCount.entrySet();
    for (Map.Entry<PlayerResults, Integer> entry : entries) {
      PlayerResults results = entry.getKey();
      Integer count = entry.getValue();
      double max = count * getGame().getMaxScore(getScoringWeights());
      double min = count * getGame().getMinScore(getScoringWeights());
      double range = max - min;
      double adjustedScore = (int) (((results.getScore() - min) / range) * 100);
      logger.info("opponent " + ++idx + ": " + results.toString() + ": "
          + adjustedScore + "/100");
    }
  }
}
